//
//  chat.c
//  LinuxPrinciplesandApplications
//
//  Created by SeanFelix‘sMacBook on 2023/12/4.
//

#include <stdio.h>          // 标准输入输出函数
#include <stdlib.h>         // 标准输入输出函数
#include <string.h>         // 字符串操作函数
#include <sys/socket.h>     // 套接字编程接口
#include <netinet/in.h>     // 套接字编程接口
#include <unistd.h>         // 对 POSIX 操作系统 API 的访问
#include <arpa/inet.h>      // inet_addr函数
#include <pthread.h>        // 包含 pthread 库

#define BUFFER_SIZE 1024    // 指定缓冲区的大小，这里设为1024字节

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 定义一个全局互斥锁，防止输入输出混乱

int maxLineLength = 50; // 假设最大行长度为80字符

// 接受消息函数
void *recv_msg(void *sock) {
    int sockfd = *((int *)sock);
    char recv_buffer[BUFFER_SIZE]; // 接收消息的缓冲区
    
    // 接收消息
    while (1) {
        memset(recv_buffer, 0, BUFFER_SIZE);
        ssize_t len = recv(sockfd, recv_buffer, BUFFER_SIZE - 1, 0); // recv函数用于从套接字读取数据到recv_buffer中。它的返回值recv_len表示接收到的字节数。
        if (len > 0) { // 如果recv_len大于0，表示成功接收到了消息并打印出来。
            recv_buffer[len-1] = '\0';
            pthread_mutex_lock(&lock); // 收到消息，上锁
            printf("\rReceived message: %s", recv_buffer);// 输出提示"\r"是返回行首的字符，它可以帮助重写当前行的内容。在这个情况下，它被用来清除"Enter message:"提示，以便打印接收到的消息。
            int messageLength = strlen(recv_buffer);
            int spacesToFill = maxLineLength - messageLength - strlen("Received message: "); // 计算需要填充的空格数
            printf("%*s", spacesToFill, ""); // 使用足够的空格填充剩余部分
            printf("\n\rEnter message to send: ");// 重新输出，等待输入
            fflush(stdout); // 确保输出被立即刷新到控制台
            pthread_mutex_unlock(&lock); // 解除上锁
            //printf("Received message: %s\n", recv_buffer);
        } else if (len == 0) { // 如果recv_len等于0，表示对方已经关闭了连接。
            printf("\nConnection closed by peer\n");
            break;
        } else { // 如果recv_len小于0，则表示接收过程中出现了错误。
            perror("Recv failed");
            break;
        }
    }
    return NULL;
}



int main(int argc, char *argv[]) {
    int sockfd;
    struct sockaddr_in addr;
    char buffer[BUFFER_SIZE];

    // 检查命令行参数
    if (argc != 3) {
        printf("Usage: %s <IP> <Port>\n", argv[0]);
        return 1;
    }

    // 创建套接字：创建了一个TCP套接字（SOCK_STREAM），用于IPv4网络通信（AF_INET）。如创建失败则输出错误消息并退出程序。
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        return 1;
    }

    // 设置地址和端口
    // memset将地址结构体清零
    memset(&addr, 0, sizeof(addr));
    // 设置协议族为IPv4
    addr.sin_family = AF_INET;
    // 设置端口号（从命令行参数转换）
    addr.sin_port = htons(atoi(argv[2]));
    // 设置IP地址（从命令行参数转换）
    addr.sin_addr.s_addr = inet_addr(argv[1]); // inet_addr函数用于将点分十进制的IP地址转换为网络字节顺序的整数形式，这是套接字地址结构需要的格式。
    
    // 连接到服务器：尝试使用之前创建的套接字连接到服务器。如果连接失败，输出错误消息并退出程序。
    if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("Connect failed");
        return 1;
    }
    printf("Connected to %s:%s\n", argv[1], argv[2]);

    // 发送和接收消息：创建循环允许用户输入消息并将其发送到服务器。fgets从标准输入读取字符串，send将字符串发送到服务器。这个循环可以通过添加代码来接收来自服务器的消息。
    
    pthread_t recv_thread;
    
    if (pthread_create(&recv_thread, NULL, recv_msg, &sockfd) != 0) { // pthread_create函数用于创建一个新线程，该线程运行recv_msg函数，负责从套接字接收消息。主线程则继续从用户那里获取消息并发送它们。
        perror("Thread creation failed");
        return 1;
    }

    fd_set read_fds;
    
    // 首次显示输入提示
    pthread_mutex_lock(&lock);
    printf("Enter message to send: ");
    fflush(stdout);
    pthread_mutex_unlock(&lock);
    
    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(STDIN_FILENO, &read_fds);
        FD_SET(sockfd, &read_fds);

        if (select(sockfd + 1, &read_fds, NULL, NULL, NULL) < 0) {
            perror("Select failed");
            break;
        }

        // 检查标准输入是否准备好
        if (FD_ISSET(STDIN_FILENO, &read_fds)) {
            pthread_mutex_lock(&lock);
            fgets(buffer, BUFFER_SIZE, stdin);
            pthread_mutex_unlock(&lock);
            send(sockfd, buffer, strlen(buffer), 0);
            printf("Enter message to send: ");
            fflush(stdout);
        }

        // 检查套接字是否有数据
        if (FD_ISSET(sockfd, &read_fds)) {
            // 接收消息的逻辑可以放在这里，或者保持在 recv_msg 线程中
        }
    }
    pthread_join(recv_thread, NULL);
    
    // 程序结束前关闭套接字
    close(sockfd);
    return 0;
}
